ChunkGenerator = class()
ChunkGenerator.GenerateSetting = {
Base = 15,
Height = 45,
SeaHeight = 32
}

function ChunkGenerator:init(world,chunkManager,seed)
    self.world = world
    self.chunkManager = chunkManager
    self.terrainBaseNoise = NoiseMap(seed,0.02,4,0.2)
    self.terrainNoise = NoiseMap(seed + 1,0.08,4,0.25)
    self.detailNoise = NoiseMap(seed + 2,0.15,4,0.35)
    self.stoneNoise3D = NoiseMap(seed + 3,0.05,1,0.2)
    self.stoneNoise = NoiseMap(seed + 4,0.06,4,0.25)
    self.chunkx,self.chunkz = 0,0
    self.maxHeight = 64
    self.rand = Random(seed)
end

function ChunkGenerator:generateChunk(chunk,index)
    self.chunkx,self.chunkz = chunk.xPos,chunk.zPos
    local biomeArray = self.chunkManager:getBiomeArrayInChunk(self.chunkx,self.chunkz)
    local blocks = self:generateTerrain(self.chunkx,self.chunkz,biomeArray)
    self:replaceBlocksForBiome(blocks,biomeArray,self.chunkx,self.chunkz)
    chunk.biomeArray = biomeArray
    chunk.blocks = blocks
    
    self.chunkManager.chunks[index] = chunk
    Thread.waitWhenGaming()
    local biome = Biome.biomeList[biomeArray[120]]
    biome:decorate(self.world,self.rand,self.chunkx,self.chunkz)
    chunk:finishCreating()
end

function ChunkGenerator:generateTerrain(cx,cz,biomeArray)
    local mh = self.maxHeight
    local terrainNoise1 = self.terrainBaseNoise:genPerlinNoise2D(cx,cz,16,16)
    local terrainNoise2 = self.terrainNoise:genPerlinNoise2D(cx,cz,16,16)
    
    --local stoneNoise = self.stoneNoise3D:genPerlinNoise3D(cx,0,cz,16,mh,16)
    local stoneNoise3D = self.stoneNoise3D
    
    local blocks = {}
    for i = 1,Chunk.Height * 256 do
        blocks[i] = 0
    end
    
    local sb,sh,seah = ChunkGenerator.GenerateSetting.Base,ChunkGenerator.GenerateSetting.Height,ChunkGenerator.GenerateSetting.SeaHeight
    local stoneId,waterId = Block.stone.id,Block.water.id
    local biomes = Biome.biomeList
    local st,biome
    
    for x = 1,16 do
        if x % 6 == 0 then
            Thread.waitWhenGaming()
        end
        for z = 1,16 do
            biome = biomes[biomeArray[x * 16 + z - 16]]
            if biome.enabled3DNoise then
                for y = 1,mh do
                    if blocks[y * 256 + x * 16 + z - 272] == 0 then
                        -- st = biome:processNoise3D(stoneNoise[x][y][z],y)
                        st = biome:processNoise3D(stoneNoise3D:getPerlinNoise3DNoOctave(cx + x,y,cz + z),y)
                        if st > 0 then
                            blocks[y * 256 + x * 16 + z - 272] = stoneId
                        elseif y <= seah then
                            blocks[y * 256 + x * 16 + z - 272] = waterId
                        end
                    end
                end
            else
                h = math.floor(biome:processNoise2D(terrainNoise1[x][z],terrainNoise2[x][z]) * sh) + sb
                for y = 1,h do
                    blocks[y * 256 + x * 16 + z - 272] = stoneId
                end
                for y = h + 1,seah do
                    blocks[y * 256 + x * 16 + z - 272] = waterId
                end
            end
        end
    end
    return blocks
end

function ChunkGenerator:replaceBlocksForBiome(blocks,biomeArray,cx,cz)   
    local stoneNoise = self.stoneNoise:genPerlinNoise2D(cx,cz,16,16)
    NoiseMap.expend2D(stoneNoise,1.2)
    
    local h,biome,biomeT,bf,bt,sn,b,i,bi
    local sb,sh = ChunkGenerator.GenerateSetting.Base,ChunkGenerator.GenerateSetting.Height
    local seah = ChunkGenerator.GenerateSetting.SeaHeight
    local mh = self.maxHeight
    local stoneId,bedRockId = Block.stone.id,Block.bedRock.id
    local biomes = Biome.biomeList
    local rand = self.rand
    local floor = math.floor
    rand:begin()
    for x = 1,16 do
        if x % 6 == 0 then
            Thread.waitWhenGaming()
        end
        for z = 1,16 do
            biome = biomes[biomeArray[x * 16 + z - 16]]
            biomeT = biome.temperature
            bf = biome.fillBlock
            bt = biome.topBlock
            bi = -1
            sn = floor((stoneNoise[x][z] - 0.5) * 6 + 4 + rand:rndDouble() / 4)
            for y = mh,1,-1 do
                i = y * 256 + x * 16 + z - 272
                if y <= rand:rndInt(1,4) then
                    blocks[i] = bedRockId
                else
                    b = blocks[i]
                    if b == 0 then
                        bi = -1
                    elseif b == stoneId then
                        if bi == -1 then
                            if sn <= 0 then
                                bf = stoneId
                                bt = 0
                            elseif y >= seah - 4 and y <= seah + 1 then
                                bf = biome.fillBlock
                                bt = biome.topBlock
                            end
                            if y < seah and bt == 0 then
                                if biomeT > 0 then
                                    bt = Block.water.id
                                else
                                    bt = Block.ice.id
                                end
                            end
                            bi = sn
                            if y >= seah - 1 then
                                blocks[i] = bt
                            else
                                blocks[i] = bf
                            end
                        elseif bi > 0 then
                            bi = bi - 1
                            blocks[i] = bf
                        end
                    end
                end
            end
        end
    end
    rand:End()
end

function ChunkGenerator:generateFlatChunk(chunk,sample)
    local blocks = {}
    local biomeArray = {}
    for i = 1,256 do
        biomeArray[i] = Biome.flat.biomeId
    end
    for x = 1,16 do
        for z = 1,16 do
            for y = 1,Chunk.Height do
                blocks[y * 256 + x * 16 + z - 272] = sample[y] or 0
            end
        end
    end
    chunk.biomeArray = biomeArray
    chunk.blocks = blocks
    chunk:finishCreating()
end
